import { Controller, Get, Query, Req, Res, Logger } from "@nestjs/common";
import { Request, Response } from "express";
import { Oauth2Service } from "./oauth2.service";
import { ConfigService } from "../config/config.service";
import {
    clientOauth2AcessToken,
    clientOauth2IdToken,
    clientOauth2RefreshToken,
    OAuthIsSkipLabel,
    refreshTokenMaxAge,
} from "../const";
@Controller()
export class Oauth2Controller {
    constructor(
        private oauth2Service: Oauth2Service,
        private readonly logger: Logger,
        private readonly configService: ConfigService
    ) {}

    @Get("/login")
    async getOauth2LoginUI(@Req() req: Request, @Res() res: Response, @Query() query: { [key: string]: string }) {
        const { redirect, lang, sso } = query;
        const state = Buffer.from(decodeURIComponent(redirect)).toString("base64");
        res.cookie("state", state, { httpOnly: true, secure: this.configService.publicInfo.https });
        res.cookie("client.origin_uri", req.url);
        res.redirect(
            `/oauth2/auth?client_id=${
                this.configService.hydraInfo.clientId
            }&response_type=code&scope=offline+openid+all&redirect_uri=${
                this.configService.hydraInfo.redirectUri
            }&state=${encodeURIComponent(state)}${lang ? `&lang=${lang}` : ""}${sso ? `&sso=${sso}` : ""}`
        );
    }

    @Get("/login/callback")
    async loginCallback(@Req() req: Request, @Res() res: Response, @Query() query: { [key: string]: string }) {
        const { code, state = "", error } = query;
        const { state: cookieState } = req.cookies || { state: "" };
        const isSkip = req.cookies?.[OAuthIsSkipLabel] === "true" ? true : false;
        const decodeState = Buffer.from(decodeURIComponent(state), "base64").toString();
        if (state === cookieState && this.oauth2Service.checkRedirect(decodeState)) {
            if (code) {
                try {
                    const data = {
                        grant_type: "authorization_code",
                        code,
                        redirect_uri: this.configService.hydraInfo.redirectUri,
                    };
                    const params = new URLSearchParams();
                    Object.keys(data).map((key) => {
                        params.append(key, data[key]);
                    });
                    const lastTimestamp = Date.now();
                    this.logger.log(`{/oauth2/token POST} start`);
                    const {
                        data: { access_token, id_token, expires_in, refresh_token },
                    } = await this.configService.hydraPublicApi.post("/oauth2/token", params, {
                        headers: {
                            "Content-Type": "application/x-www-form-urlencoded",
                            Authorization: `Basic ${Buffer.from(
                                `${encodeURIComponent(this.configService.hydraInfo.clientId)}:${encodeURIComponent(
                                    this.configService.hydraInfo.clientSecret
                                )}`
                            ).toString("base64")}`,
                        },
                    });
                    this.logger.log(`{/oauth2/token POST} success  +${Date.now() - lastTimestamp}ms`);

                    const isMaxAge = Number(expires_in) || Number(expires_in) === 0 || Number(expires_in) === -0;
                    const isSetSessionExpires = isMaxAge && isSkip;
                    const maxAge = isSetSessionExpires ? { maxAge: expires_in * 1000 } : {};
                    const refreshMaxAge = isSetSessionExpires ? { maxAge: refreshTokenMaxAge } : {};

                    res.cookie(clientOauth2AcessToken, access_token, {
                        httpOnly: false,
                        secure: this.configService.publicInfo.https,
                        ...maxAge,
                    });
                    res.cookie(clientOauth2IdToken, id_token, {
                        httpOnly: false,
                        secure: this.configService.publicInfo.https,
                        ...maxAge,
                    });
                    res.cookie(clientOauth2RefreshToken, refresh_token, {
                        httpOnly: false,
                        secure: this.configService.publicInfo.https,
                        ...refreshMaxAge,
                    });
                    res.clearCookie("state");
                    return res.render("iframe", { url: decodeState });
                } catch (e) {
                    this.logger.error(
                        `{/oauth2/token POST} error`,
                        `${JSON.stringify(e && e.response && e.response.data)}`
                    );
                    if (e && e.response && e.response.status !== 503) {
                        const { status, data } = e.response;
                        res.statusCode = status;
                        return res.render("login-err", {
                            url: `/oauth2/error?${
                                (
                                    data &&
                                    Object.keys(data).map(
                                        (key) => `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`
                                    )
                                ).join("&") || ""
                            }`,
                        });
                    } else {
                        this.logger.error("内部错误，连接hydra服务失败");
                        res.statusCode = 500;
                        return res.render("login-err", {
                            url: `/oauth2/error?code=500040002&cause=${encodeURIComponent(
                                "内部错误"
                            )}&message=${encodeURIComponent("连接hydra服务失败")}`,
                        });
                    }
                }
            } else {
                this.logger.error("参数不合法，缺少code参数");
                res.statusCode = 400;
                return res.render("login-err", {
                    url: `/oauth2/error?code=400041000&cause=${encodeURIComponent(
                        "参数不合法"
                    )}&message=${encodeURIComponent("缺少code参数")}`,
                });
            }
        } else {
            this.logger.error("参数不合法，state验证失败");
            res.statusCode = 400;
            return res.render("login-err", {
                url: `/oauth2/error?code=400041003&cause=${encodeURIComponent(
                    "参数不合法"
                )}&message=${encodeURIComponent(
                    "state验证失败，可能cookie与querystring中state值不一致、state值不是以/开始的字符串等等"
                )}`,
            });
        }
    }

    @Get("/logout")
    async getOauth2LogoutUI(@Res() res: Response, @Req() req: Request, @Query() query: { [key: string]: string }) {
        const { redirect } = query;
        const { id_token } = req.cookies || { id_token: "" };
        if (id_token) {
            const state = Buffer.from(decodeURIComponent(redirect)).toString("base64");
            res.cookie("state", state, { httpOnly: true, secure: this.configService.publicInfo.https });
            res.redirect(
                `/oauth2/sessions/logout?id_token_hint=${id_token}&post_logout_redirect_uri=${
                    this.configService.hydraInfo.postLogoutRedirectUri
                }&state=${encodeURIComponent(state)}`
            );
        } else {
            res.clearCookie(clientOauth2AcessToken);
            res.clearCookie(clientOauth2RefreshToken);
            res.clearCookie("id_token");
            res.clearCookie("state");
            res.redirect(redirect);
        }
    }

    @Get("/logout/callback")
    async logoutCallback(@Req() req: Request, @Res() res: Response, @Query() query: { [key: string]: string }) {
        const { state = "" } = query;
        const { state: cookieState } = req.cookies || { state: "", token: "" };
        const token = req.cookies[`client.oauth2_token`];
        const decodeState = Buffer.from(decodeURIComponent(state), "base64").toString();
        if (state === cookieState && this.oauth2Service.checkRedirect(decodeState)) {
            if (token) {
                try {
                    const data = {
                        token: token,
                    };
                    const params = new URLSearchParams();
                    Object.keys(data).map((key) => {
                        params.append(key, data[key]);
                    });
                    const lastTimestamp = Date.now();
                    this.logger.log(`{oauth2/revoke POST} start`);
                    await this.configService.hydraPublicApi.post("oauth2/revoke", params, {
                        headers: {
                            "Content-Type": "application/x-www-form-urlencoded",
                            Authorization: `Basic ${Buffer.from(
                                `${encodeURIComponent(this.configService.hydraInfo.clientId)}:${encodeURIComponent(
                                    this.configService.hydraInfo.clientSecret
                                )}`
                            ).toString("base64")}`,
                        },
                    });
                    this.logger.log(`{oauth2/revoke POST} success +${Date.now() - lastTimestamp}ms`);
                } catch (e) {
                    this.logger.error(
                        `{oauth2/revoke POST} error`,
                        `${JSON.stringify(e && e.response && e.response.data)}`
                    );
                    if (e && e.response && e.response.status !== 503) {
                        const { status, data } = e.response;
                        res.statusCode = status;
                        return res.render("login-err", {
                            url: `/oauth2/error?${
                                (
                                    data &&
                                    Object.keys(data).map(
                                        (key) => `${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`
                                    )
                                ).join("&") || ""
                            }`,
                        });
                    } else {
                        this.logger.error("内部错误，连接hydra服务失败");
                        res.statusCode = 500;
                        return res.render("login-err", {
                            url: `/oauth2/error?code=500040002&cause=${encodeURIComponent(
                                "内部错误"
                            )}&message=${encodeURIComponent("接hydra服务失败")}`,
                        });
                    }
                }
                res.clearCookie("state");
                res.clearCookie(clientOauth2RefreshToken);
                res.clearCookie(clientOauth2AcessToken);
                res.clearCookie("id_token");
            }
            res.redirect(decodeState);
        } else {
            this.logger.error("参数不合法，state验证失败");
            res.statusCode = 400;
            res.render("login-err", {
                url: `/oauth2/error?code=400041003&cause=${encodeURIComponent(
                    "state验证失败"
                )}&message=${encodeURIComponent(
                    "可能cookie与querystring中state值不一致、state值不是以/开始的字符串等等"
                )}`,
            });
        }
    }

    @Get("/login/refreshToken")
    async getRefreshToken(@Req() req: Request, @Res() res: Response, @Query() query: { [key: string]: string }) {
        const refreshToken = req.cookies[clientOauth2RefreshToken];
        const isSkip = req.cookies?.[OAuthIsSkipLabel] === "true" ? true : false;
        if (refreshToken) {
            try {
                const data = {
                    grant_type: "refresh_token",
                    refresh_token: refreshToken,
                };
                const params = new URLSearchParams();
                Object.keys(data).map((key) => {
                    params.append(key, data[key]);
                });
                const lastTimestamp = Date.now();
                this.logger.log(`{/oauth2/token POST} start`);
                const {
                    data: { access_token, id_token, expires_in, refresh_token },
                } = await this.configService.hydraPublicApi.post("/oauth2/token", params, {
                    headers: {
                        "Content-Type": "application/x-www-form-urlencoded",
                        Authorization: `Basic ${Buffer.from(
                            `${encodeURIComponent(this.configService.hydraInfo.clientId)}:${encodeURIComponent(
                                this.configService.hydraInfo.clientSecret
                            )}`
                        ).toString("base64")}`,
                    },
                });
                this.logger.log(`{/oauth2/token POST} success  +${Date.now() - lastTimestamp}ms`);

                const isMaxAge = Number(expires_in) || Number(expires_in) === 0 || Number(expires_in) === -0;
                const isSetSessionExpires = isMaxAge && isSkip;
                const maxAge = isSetSessionExpires ? { maxAge: expires_in * 1000 } : {};
                const refreshMaxAge = isSetSessionExpires ? { maxAge: refreshTokenMaxAge } : {};

                res.cookie(clientOauth2AcessToken, access_token, {
                    httpOnly: false,
                    secure: this.configService.publicInfo.https,
                    ...maxAge,
                });
                res.cookie("id_token", id_token, {
                    httpOnly: false,
                    secure: this.configService.publicInfo.https,
                    ...maxAge,
                });
                res.cookie(clientOauth2RefreshToken, refresh_token, {
                    httpOnly: false,
                    secure: this.configService.publicInfo.https,
                    ...refreshMaxAge,
                });
                res.clearCookie("state");
                return res.json({
                    code: 200,
                    message: "ok",
                });
            } catch (e) {
                this.logger.error(
                    `{/oauth2/token POST} error`,
                    `${JSON.stringify(e && e.response && e.response.data)}`
                );
                if (e && e.response && e.response.status !== 503) {
                    const { status, data } = e.response;
                    res.statusCode = status;
                    return res.json({
                        code: status,
                        message: data,
                    });
                } else {
                    this.logger.error("内部错误，连接hydra服务失败");
                    const status = 500;
                    res.statusCode = status;
                    return res.json({
                        code: status,
                        message: "内部错误，连接hydra服务失败",
                    });
                }
            }
        } else {
            this.logger.error("参数不合法，缺少refreshToken");
            const status = 400;
            res.statusCode = status;
            return res.json({
                code: status,
                message: "参数不合法，缺少refreshToken",
            });
        }
    }
}
